#ifndef OCLLAPACK_H
#define	OCLLAPACK_H

#include <CL/cl.h>
#include "../LapackPlatform/Lapack.h"
#include "../clUtils.h"
#include "../ClEvent.h"

struct cl_ptr {
	cl_mem handle;
	size_t offset;
	
	cl_ptr(cl_mem mem) : handle(mem), offset(0) { }
	
	cl_ptr(cl_mem mem, size_t _offset) : handle(mem), offset(_offset) { }
};

inline cl_ptr ref(const cl_ptr &p, size_t index) {
	return cl_ptr(p.handle, p.offset + index);
}

struct OclLapack : Lapack {	
	OclLapack(cl_device_type deviceType);
	OclLapack(cl_device_id deviceId);
	
	virtual ~OclLapack();	
	
	virtual int sgemv(char *trans, int *m, int *n, float *alpha, 
		float *a, int *lda, float *x, int *incx, float *beta, float *y, 
		int *incy);
	
	void sgemv(char *trans, int m, int n, float alpha, 
		const cl_ptr a, int lda,
		const cl_ptr x, int incX,
		float beta,
		cl_ptr y, int incY) ;
	
	virtual int ssymv(char *uplo, int *n, float *alpha, float *a, 
		int *lda, float *x, int *incx, float *beta, float *y, int *
		incy);
	
	void ssymv(char *uplo, int n, float alpha,
		const cl_ptr a, int lda,
		const cl_ptr x, int incX,
		float beta,
		cl_ptr y, int incY);
	
	virtual int ssyr2(char *uplo, int *n, float *alpha, float *x, 
		int *incx, float *y, int *incy, float *a, int *lda);
	
	void ssyr2(char* uplo, int n, float alpha,
		const cl_ptr x, int incX,
		const cl_ptr y, int incY,
		cl_ptr a, int lda);
	
	virtual int sger(int *m, int *n, float *alpha, float *x, 
		int *incx, float *y, int *incy, float *a, int *lda);
	
	void sger(int m, int n, float alpha, 
		const cl_ptr x, int incX,
		const cl_ptr y, int incY,
		cl_ptr a, int lda);
	
	// TODO test it
	virtual int sgemm(char *transa, char *transb, int *m, int *
		n, int *k, float *alpha, float *a, int *lda, float *b, int *
		ldb, float *beta, float *c, int *ldc);
	
	void sgemm(const char* transA, const char* transB, int m, int n, int k, float alpha,
		const cl_ptr a, int lda,
		const cl_ptr b, int ldb,
		float beta,
		cl_ptr c, int ldc);
	
	// TODO test it
	virtual int strmm(char *side, char *uplo, char *transa, char *diag, 
		int *m, int *n, float *alpha, float *a, int *lda, float *b, 
		int *ldb);
	
	void strmm(const char* side,
		const char* uplo, 
		const char* transA,
		const char* diag,
		int m, int n, float alpha,
		const cl_ptr a, int lda,
		cl_ptr b, int ldb);
	
	// TODO test it
	virtual int ssyr2k(char *uplo, char *trans, int *n, int *k, 
		float *alpha, float *a, int *lda, float *b, int *ldb, float *beta, 
		float *c, int *ldc);
	
	void ssyr2k(const char* uplo, const char* trans, int n, int k, float alpha,
		const cl_ptr a, int lda,
		const cl_ptr b, int ldb,
		float beta,
		cl_ptr c, int ldc);
	
	virtual int slarf(char *side, int *m, int *n, float *v, 
		int *incv, float *tau, float *c, int *ldc, float *work);
	
	void slarf(const char* side, int m, int n, 
		const cl_ptr v, int incV,
		float tau,
		cl_ptr c, int ldc,
		cl_ptr work);
	
	virtual int sscal(int *n, float *sa, float *sx, int *incx);
	
	void sscal(int n, float sa, cl_ptr sx, int incX);
	
	virtual int sorg2r(int *m, int *n, int *k, float *a, 
		int *lda, float *tau, float *work, int *info);
	
	void sorg2r(int m, int n, int k, 
		float* a, int offsetA, int lda,
		float* tau, int offsetTau,
		float* work, int offsetWork);
		
	virtual int sstebz(char *range, char *order, int *n, float *vl,
		float *vu, int *il, int *iu, float *abstol, float *d, float *e,
		int *m, int *nsplit, float *w, int *iblock, int *
		isplit, float *work, int *iwork, int *info);
	
private:
	static const char* programScaleSrc;
		
	cl_device_id deviceId;
    cl_context context;
    cl_command_queue queue;
	
	cl_program programScale;
	cl_kernel scale1;
	cl_kernel scaleN;
	
	cl_program programEigenValues;
	cl_kernel recalculateEigenInterval;	
	
	cl_kernel calculateNumEigensInInterval;
	cl_kernel recalculateInterval;
	
	void init(cl_device_id deviceId);
		
	ClEvent recalculateEigenIntervals(size_t intervalCount, int n,
		cl_mem d, cl_mem e,
		cl_mem inIntervals,	cl_mem outIntervals,
		cl_mem inNumLess, cl_mem outNumLess,		
		float abstol, float reltol, float pivmin);
	
	int convergeIntervals(int n, int maxIterations, 
		float *d, float *e,
		void* intervals,
		int *numLess,
		float abstol, float reltol, float pivmin); 
		
	ClEvent calculateNumEigensInIntervals(size_t numEigens,
		cl_mem numEigensInInterval, cl_mem intervals,
		int n, cl_mem d, cl_mem e,
		float pivmin);
	
	ClEvent recalculateIntervals(size_t numEigens,
		int n, cl_mem d, cl_mem e,
		cl_mem inIntervals, cl_mem outIntervals,
		cl_mem numEigensInIntervals,
		float abstol, float reltol, float pivmin);
	
	template <typename T>
	cl_mem createBuffer(T* contents, size_t count) {
		cl_int errorCode;
		cl_mem handle = clCreateBuffer(context, 
				CL_MEM_READ_WRITE | CL_MEM_COPY_HOST_PTR, 
				count * sizeof(T), 
				(void*)contents, &errorCode);
		CL_CHECK(errorCode);
		return handle;
	}
	
	template <typename T>
	cl_mem createBuffer(size_t count) {
		cl_int errorCode;
		cl_mem handle = clCreateBuffer(context, CL_MEM_READ_WRITE, count * sizeof(T), NULL, &errorCode);
		CL_CHECK(errorCode);
		return handle;
	}
};

#endif	/* OCLLAPACK_H */

